﻿using System;
using System.Web;
using System.Web.Routing;
using ManagedUIFlow.Infrastructure.Commands;
using ManagedUIFlow.Model;
using Moq;
using Xunit;

namespace ManagedUIFlow.Specs.Model
{
    public class CommandBuilderSpecs
    {
        private readonly RequestContext _requestContext;

        public CommandBuilderSpecs()
        {
            _requestContext = new RequestContext(new Mock<HttpContextBase>().Object, new RouteData());
        }

        [Fact]
        public void CommandBuilder_ByDefault_ReturnsDefaultCommandBuilder()
        {
            var cb = new CommandBuilder();

            var cf = cb.GetCommandFactory();

            Assert.IsType(typeof(DefaultCommandFactory), cf);
        }

        [Fact]
        public void CreateCommandWithFactoryThatCannotBeCreated_Throws()
        {
            var cb = new CommandBuilder();
            cb.SetCommandFactory(typeof(CommandFactoryThrowsFromConstructor));

            Assert.Throws<InvalidOperationException>(() =>
                                                         {
                                                             var flow = new ManagedFlow("foo");
                                                             var appController = new ApplicationController
                                                                                     {
                                                                                         CommandBuilder = cb
                                                                                     };
                                                             appController.Execute(_requestContext, flow);
                                                         });               
        }

        [Fact]
        public void CreateCommandWithFactoryThatReturnsNull_Throws()
        {
            // Arrange
            var cb = new CommandBuilder();
            cb.SetCommandFactory(typeof(CommandFactoryReturnsNull));

            Assert.Throws<InvalidOperationException>("The ICommandFactory 'System.Web.Mvc.Test.ControllerBuilderTest+ControllerFactoryReturnsNull' did not return a command for a command named 'boo'.",
                                                     () =>
                                                         {
                                                             var flow = new ManagedFlow("boo");
                                                             var appController = new ApplicationController
                                                                                     {
                                                                                         CommandBuilder = cb
                                                                                     };
                                                             appController.Execute(_requestContext, flow);
                                                         });                  
        }

        [Fact]
        public void CreateControllerWithFactoryThatThrowsDoesNothingSpecial()
        {
            var cb = new CommandBuilder();
            cb.SetCommandFactory(typeof(CommandFactoryThrows));

            Assert.Throws<Exception>(() =>
                                         {
                                             var flow = new ManagedFlow("foo");
                                             var appController = new ApplicationController
                                                                     {
                                                                         CommandBuilder = cb
                                                                     };
                                             appController.Execute(_requestContext, flow);
                                         });     
        }

        [Fact]
        public void CreateControllerWithFactoryInstanceReturnsInstance()
        {
            var cb = new CommandBuilder();
            var factory = new DefaultCommandFactory();
            cb.SetCommandFactory(factory);

            var cf = cb.GetCommandFactory();

            Assert.Same(factory, cf);
        }

        [Fact]
        public void CreateControllerWithFactoryTypeReturnsValidType()
        {
            var cb = new CommandBuilder();
            cb.SetCommandFactory(typeof(MockCommandFactory));

            var cf = cb.GetCommandFactory();

            Assert.IsType(typeof(MockCommandFactory), cf);
        }

        [Fact]
        public void SetControllerFactoryInstanceWithNullThrows()
        {
            var cb = new CommandBuilder();

            Assert.Throws<ArgumentNullException>(() => cb.SetCommandFactory((ICommandFactory) null));
        }

        [Fact]
        public void SetControllerFactoryTypeWithNullThrows()
        {
            var cb = new CommandBuilder();

            Assert.Throws<ArgumentNullException>(() => cb.SetCommandFactory((Type)null));
        }

        [Fact]
        public void SetControllerFactoryTypeWithNonFactoryTypeThrows()
        {
            var cb = new CommandBuilder();

            Assert.Throws<ArgumentException>(
                "The command factory type 'System.Int32' must implement the ICommandFactory interface.\r\nParameter name: commandFactoryType", 
                () => cb.SetCommandFactory(typeof(int))
                );            
        }

        public class CommandFactoryThrowsFromConstructor : ICommandFactory
        {
            public CommandFactoryThrowsFromConstructor()
            {
                throw new Exception("CommandFactoryThrowsFromConstructor");
            }

            public ICommand CreateCommand(RequestContext requestContext, ManagedFlow flow)
            {
                return null;
            }

            public void ReleaseCommand(ICommand command)
            {
            }
        }

        public class CommandFactoryReturnsNull : ICommandFactory
        {
            public ICommand CreateCommand(RequestContext requestContext, ManagedFlow flow)
            {
                return null;
            }

            public void ReleaseCommand(ICommand command)
            {
            }
        }

        public class CommandFactoryThrows : ICommandFactory
        {
            public ICommand CreateCommand(RequestContext requestContext, ManagedFlow flow)
            {
                throw new Exception("CommandFactoryThrows");
            }

            public void ReleaseCommand(ICommand command)
            {
            }
        }

        public class MockCommandFactory : ICommandFactory
        {

            public ICommand CreateCommand(RequestContext requestContext, ManagedFlow flow)
            {
                throw new NotImplementedException();
            }

            public void ReleaseCommand(ICommand command)
            {
            }
        }       
    }
}